/**
 * @file nivel1a.c
 * @brief Implementación de las funciones del modulo nivel1a
 *
 * Fichero que contiene el cuerpo de las funciones definidas por la
 * biblioteca nivel1a
 */

#include "nivel1a.h"

/**
 * Enumeración de tipos ethernet del paquete
 * Este enum define un tipo -interno a la biblioteca- que representa el tipo ethernet
 * de un paquete recibido.
 */
typedef enum { A_ETH_800, A_ETH_806, A_ETH_MALFORMED } TIPO_ETH;

#define ETHERTYPE_1_1 0x08
#define ETHERTYPE_1_2 0x00
#define ETHERTYPE_2_1 0x08
#define ETHERTYPE_2_2 0x06

/**
 * Semaforo de proteccion contra reentrada a la funcion
 * de envio de datos.
*/
pthread_mutex_t sendmsg_mutex = PTHREAD_MUTEX_INITIALIZER;

/**
 * Flag que contiene el estado del nivel 1a
 */
int Nivel1aIniciado = 0;

/**
 * Punteros a las funciones de notificacion de nivel superior
 */
tpfNotificacionDesdeNivel1a nivel_1a_callback_eth1 = NULL;
tpfNotificacionDesdeNivel1a nivel_1a_callback_eth2 = NULL;

/**
 * Array con la dirección mac del equipo usando la biblioteca
 */
BYTE A_DIR_MAC[ETH_ALEN];

/**
 * Flag que marca la aceptación de paquetes de tipo 800
 */
int enable_eth_800 = 0;

/**
 * Flag que marca la aceptación de paquetes de tipo 806
 */
int enable_eth_806 = 0;

/**
 * Estructura que representa una cabecera ethernet
 * La presente estructura representa un conjunto de punteros a los diversos
 * campos de una cabecera ethernet. Los mismos no apuntan a zonas en el heap,
 * sino que se hacen apuntar a los índices adecuados de un buffer con un
 * mensaje para facilitar su lectura
 */
typedef struct {
  const BYTE * dir_destino; /**< Dirección MAC del destinatario del paquete */
  const BYTE * dir_origen; /**< Dirección MAC del orígen del paquete */
  TIPO_ETH tipo_trama; /**< Tipo de la trama (ya parseado) */
} CABECERA_ETH;


#define ETH_TYPE_1 (2*ETH_ALEN) /* Posición del primer byte del ethertype */
#define ETH_TYPE_2 (ETH_TYPE_1 + 1) /* Posición del segundo byte del ethertype */

#define SEM_KEY 1597

/**
 * Función que prepara la cabecera de un mensaje para su acceso
 * La función lee_cabecera toma un buffer de bytes (un mensaje) y un puntero a una 
 * estructura de tipo cabecera y configura los punteros internos de la misma para que
 * apunten a las secciones adecuadas del buffer como paquete ethernet
 *
 * @param mensaje Buffer que contiene el mensaje (la consistencia de este queda bajo la responsabilidad del usurio de la función: PASAR UN MENSAJE INCONSISTENTE (NULL/TAMAÑO MENOR QUE EL MÍNIMO) GENERARÁ FALLO DE SEGMENTACIÓN
 * @param tamano Tamano del mensaje en bytes (por consistencia con otras funciones similares)
 * @param cabecera Puntero a la cabecera donde se leerán los datos
 * @return Puntero a la posición siguiente al final de la cabecera del mensaje en el buffer
 */
const BYTE * lee_cabecera(const BYTE * mensaje, int tamano, CABECERA_ETH * cabecera) {
  /* Lectura de direcciones MAC */
  cabecera->dir_destino = mensaje;
  cabecera->dir_origen = mensaje + ETH_ALEN;

  /* Lectura del ethertype */
  if(mensaje[ETH_TYPE_1] == ETHERTYPE_1_1 && mensaje[ETH_TYPE_2] == ETHERTYPE_1_2)
    cabecera->tipo_trama = A_ETH_800;
  else if(mensaje[ETH_TYPE_1] == ETHERTYPE_2_1 && mensaje[ETH_TYPE_2] == ETHERTYPE_2_2) 
    cabecera->tipo_trama = A_ETH_806;
  else
    cabecera->tipo_trama = A_ETH_MALFORMED;

  return mensaje + TAMANO_CABECERA;
}
#undef ETH_TYPE_1
#undef ETH_TYPE_2

/**
 * Función que calcula el padding necesario, dado el tamaño del contenido de un mensaje,
 * para que éste alcance la longitud mínima
 * 
 * @param tamano Tamaño del contenido del mensaje
 * @return Tamaño del padding necesario
 */
int calcula_padding(int tamano) {
  if(tamano + TAMANO_CABECERA < ETH_FRAME_MIN) {
    return ETH_FRAME_MIN - tamano - TAMANO_CABECERA;
  } else {
    return 0;
  }
}

/**
 * Función que escribe la cabecera de un mensaje
 * La función pon_cabecera escribe los datos de una cabecera sobre un buffer dado
 * Se asume que dicho buffer tiene espacio suficiente para los mismos
 *
 * @param mensaje Puntero al buffer a llenar
 * @param cabecera Puntero a la estructura de la que se obtienen los datos
 * @return Puntero al primer byte del buffer tras la cabecera
 */
BYTE * pon_cabecera(BYTE * mensaje, const CABECERA_ETH * cabecera) {
  BYTE * cur_ptr = mensaje; /* Variable que almacena la posición del cursor de escritura */

  /* Escritura de las direcciones */
  memcpy(cur_ptr, cabecera -> dir_destino, ETH_ALEN);
  cur_ptr = cur_ptr + ETH_ALEN;
  memcpy(cur_ptr, cabecera -> dir_origen, ETH_ALEN);
  cur_ptr = cur_ptr + ETH_ALEN;

  /* Escritura del tipo ethernet */
  switch(cabecera -> tipo_trama) {
  case(A_ETH_800):
    *(cur_ptr++) = ETHERTYPE_1_1;
    *(cur_ptr++) = ETHERTYPE_1_2;
    break;
  case(A_ETH_806):
    *(cur_ptr++) = ETHERTYPE_2_1;
    *(cur_ptr++) = ETHERTYPE_2_2;
    break;
  default:
    return NULL;
  }
  return cur_ptr;
}

/**
 * Función que copia el cuerpo de un mensaje en un buffer
 * La función pon_mensaje vuelca el contenido de un mensaje
 * sobre un buffer
 * 
 * @param buffer Puntero a la posición sobre la que se copiará el mensaje
 * @param mensaje Puntero al mensaje
 * @param tamano Tamaño del mensaje
 * @return Puntero a la primera posición no escrita del buffer
 */
BYTE * pon_mensaje(BYTE * buffer, const BYTE * mensaje, int tamano) {
  BYTE * cur_ptr = buffer;
  int i;
  int padding = calcula_padding(tamano);
  /* Copiado del mensaje */
  memcpy(cur_ptr, mensaje, tamano);
  cur_ptr += tamano;
  /* Inserción del padding */
  for(i = 0; i < padding; i++)
    *(cur_ptr++) = 0x00;
  return cur_ptr;
}

/**
 * Función que computa la validez de un mensaje
 * @param mensaje Puntero al mensaje
 * @param tamano Tamaño total del mensaje
 * @return 1 si el mensaje es válido, 0 de otro modo
 */
int mensaje_valido(const BYTE * mensaje, int tamano) {
  CABECERA_ETH cabecera;

  if(tamano < TAMANO_CABECERA)
    return 0;

  lee_cabecera(mensaje, tamano, &cabecera);
  
  if(cabecera.tipo_trama != A_ETH_MALFORMED) {
    if(cabecera.tipo_trama == A_ETH_800 && enable_eth_800)
      return 1;
    else if(cabecera.tipo_trama == A_ETH_806 && enable_eth_806)
      return 1;
    else
      return 0;
  }
  else
    return 0;
}

/**
 * Función de notificación de nivel 1
 * Función que el nivel 1 emplea para verificar si ha de delegar el procesado de un mensaje
 * al nivel superior o ha de descartar el mismo
 * @param tamano Tamaño del mensaje recibido
 * @param mensaje Puntero al mensaje recibido (SE ASUME CORRECTO)
 * @param timestamp Puntero a la estructura con los datos de tiempo de la llegada del mensaje
 * @return ETH_OK si el mensaje pertoca al nivel superior, ETH_ERROR s ha de ser descartado
 */
int notificacion_nivel_1(int tamano, const BYTE * mensaje, struct timeval * timestamp) {
  CABECERA_ETH cabecera;
  int tipo;

  const BYTE * mensaje_ptr;
  
  if(mensaje_valido(mensaje, tamano)) {
    mensaje_ptr = lee_cabecera(mensaje, tamano, &cabecera);
    switch(cabecera.tipo_trama) {
    case(A_ETH_800):
      tipo = 1;
      if(nivel_1a_callback_eth1 != NULL)
	return nivel_1a_callback_eth1(cabecera.dir_origen, tamano - TAMANO_CABECERA, mensaje_ptr, tipo, timestamp);
      else
	return ETH_ERROR;
      break;
    case(A_ETH_806):
      tipo = 2;
      if(nivel_1a_callback_eth2 != NULL)
	return nivel_1a_callback_eth2(cabecera.dir_origen, tamano - TAMANO_CABECERA, mensaje_ptr, tipo, timestamp);
      else
	return ETH_ERROR;
      break;
    default:
      return ETH_ERROR;
    }
  } else {
    return ETH_ERROR;
  }
}

/**
 * Función de inicialización del nivel 1a
 * Descrita en cabeceras
 */
int InicializarNivel1a(int Tipo1, int Tipo2, tpfNotificacionDesdeNivel1a Funcion, int Timeout) {
  int nTipos = 0;
  BYTE tipos_eth[4];
  int prev_eth_800 = enable_eth_800;
  int prev_eth_806 = enable_eth_806;
  /*  tpfNotificacionDesdeNivel1a prev_callback_800 = nivel_1a_callback_eth1;
      tpfNotificacionDesdeNivel1a prev_callback_806 = nivel_1a_callback_eth2; */

  if(Nivel1aIniciado) {
    FinalizarNivel1a();
  }
  
  /* Buffe temporal para los ethertypes */
  tipos_eth[0] = ETHERTYPE_1_1;
  tipos_eth[1] = ETHERTYPE_1_2;
  tipos_eth[2] = ETHERTYPE_2_1;
  tipos_eth[3] = ETHERTYPE_2_2;

  if(Tipo1 == 0 && Tipo2 == 0) {
    /*semctl(semId, 0, IPC_RMID);*/
    return ETH_ERROR;
  }

  if(Tipo1 || prev_eth_800) {
    nivel_1a_callback_eth1 = (Tipo1)?Funcion:nivel_1a_callback_eth1;
    nTipos ++;
    enable_eth_800 = 1;
  }
  if(Tipo2 || prev_eth_806) {
    nivel_1a_callback_eth2 = (Tipo2)?Funcion:nivel_1a_callback_eth2;
    nTipos ++;
    enable_eth_806 = 1;
  }
  /*
  if(Tipo1) {
    nivel_1a_callback_eth1 = Funcion;
    nTipos ++;
    enable_eth_800 ++;
  }
  if(Tipo2) {
    nivel_1a_callback_eth2 = Funcion;
    nTipos ++;
    enable_eth_806 ++;
    }*/
  
  /* Llamada a la función inferior */
  if(IniciarNivel1(nTipos,&(tipos_eth[(Tipo1?0:2)]) , notificacion_nivel_1, Timeout) == ETH_OK) {
    Nivel1aIniciado = 1;
    /* Obtención de dirección MAC */
    ObtenerDirMAC(A_DIR_MAC);
    return ETH_OK;
  } else {
    return ETH_ERROR;
  }
}

/**
 * Función que finaliza el nivel 1a
 * Documentada en fichero de cabeceras
 */
int FinalizarNivel1a(void) {
  if(Nivel1aIniciado) {
    if(FinalizarNivel1() == ETH_OK) {
      Nivel1aIniciado = 0;
      enable_eth_800 = enable_eth_806 = 0;
      return ETH_OK;
    } else {
      fputs("Problema al finalizar libreria de nivel 1a", stderr);
        Nivel1aIniciado = 0;
	    enable_eth_800 = enable_eth_806 = 0;
      return ETH_ERROR;
    }
  } else {
    return ETH_OK;
  }
}

/**
 * Función para enviar datagramas
 * Documentada en fichero de cabeceras 
 */
int EnviarDatagramaNivel1a(const BYTE * DireccionDestino, int Tamano, const BYTE * Mensaje, int Tipo) {
  BYTE trama_nivel_1[ETH_FRAME_MAX];
  BYTE * cur_ptr;
  CABECERA_ETH cabecera;
  int status;

  /* Comprobación de validez de datos */
  /* Exhaustiva, al ser función externa */
  if(! Nivel1aIniciado || (Tipo != 1 && Tipo != 2) || Tamano < 0 || (Tamano > 0 && Mensaje == NULL) || DireccionDestino == NULL || (Tamano + TAMANO_CABECERA) > ETH_FRAME_MAX || Tamano > ETH_DATA_MAX )
    return ETH_ERROR;

  pthread_mutex_lock(&sendmsg_mutex);
  {																						
  	/* Configuración de la cabecera */
  	cabecera.dir_destino = DireccionDestino;
  	cabecera.dir_origen = A_DIR_MAC;
  	switch(Tipo) {
  	  case(1):
    	cabecera.tipo_trama = A_ETH_800;
    	break;
  	  case(2):
    	cabecera.tipo_trama = A_ETH_806;
    	break;
  	  default:
  	    pthread_mutex_unlock(&sendmsg_mutex);
     	return ETH_ERROR;
  	}

  	/* Escritura de cabecera y mensaje */
  	cur_ptr = pon_cabecera(trama_nivel_1, &cabecera);
  	cur_ptr = pon_mensaje(cur_ptr, Mensaje, Tamano);

  	status=EnviarTramaNivel1(cur_ptr - trama_nivel_1, trama_nivel_1);
  
  }
  pthread_mutex_unlock(&sendmsg_mutex);	
  
  return status;
}

/**
 * Función para recibir datagramas
 * Documentada en fichero de cabeceras
 */
int RecibirDatagramaNivel1a(BYTE * DireccionRemitente, BYTE * Mensaje, int * Tipo, struct timeval* time) {
  BYTE buffer[ETH_FRAME_MAX];
  CABECERA_ETH cabecera;
  int tamano_recibido;
  const BYTE * ptr_mensaje;

  /* Control de errores */
  if(!Nivel1aIniciado || DireccionRemitente==NULL || Mensaje==NULL || Tipo==NULL || time==NULL) {
    return ETH_ERROR;
  }

  /* Recepción de trama */
  tamano_recibido = RecibirTramaNivel1(buffer, time);
  if(tamano_recibido == 0 || tamano_recibido == ETH_ERROR || !mensaje_valido(buffer, tamano_recibido)) {
    return ETH_ERROR;
  }
  
  /* Lectura de cabecera */
  ptr_mensaje = lee_cabecera(buffer, tamano_recibido, &cabecera);

  /* Lectura de tipo */
  switch(cabecera.tipo_trama) {
  case(A_ETH_800):
    *Tipo = 1;
    break;
  case(A_ETH_806):
    *Tipo = 2;
    break;
  case(A_ETH_MALFORMED):
    return ETH_ERROR;
  }
  
  /* Copiado de remitente */
  memcpy(DireccionRemitente, cabecera.dir_origen, ETH_ALEN);
 
  /* Copiado de mensaje */
  memcpy(Mensaje, ptr_mensaje, tamano_recibido - TAMANO_CABECERA);

  return tamano_recibido - TAMANO_CABECERA;
}

int EsperaNuevoDatagrama(int timeout)
{
	return EsperaNuevaTrama(timeout);
}
  
